package cn.ultrarisk.sprinx.mybatis.query;

import cn.ultrarisk.sprinx.mybatis.config.MybatisContext;
import cn.ultrarisk.sprinx.util.Utilities;

import java.util.*;

/**
 * Query
 *
 * @author sevendlong(lin23871@163.com)
 * @date 2014/9/30
 * @since V1.0
 */
public class Query {
    private List<Criteria> criterias;
    private Logical logical = Logical.AND;
    private LinkedHashMap<String, Direction> sorts;
    private List<String> groups;
    private Integer start;
    private Integer limit;
    private Map<String, Object> attach;

    public Query() {
    }

    public static Query query(String key, Object value) {
        if (value != null && List.class.isAssignableFrom(value.getClass())) {
            return new Query().in(key, value);
        }
        return new Query().eq(key, value);
    }

    public static Query query(String key, Criteria.Opt opt, Object value) {
        return new Query().addCriteria(key, opt, value);
    }

    public static Query eqQuery(String key, Object value) {
        return new Query().eq(key, value);
    }

    public static Query neqQuery(String key, Object value) {
        return new Query().neq(key, value);
    }

    public static Query gtQuery(String key, Object value) {
        return new Query().gt(key, value);
    }

    public static Query gteQuery(String key, Object value) {
        return new Query().gte(key, value);
    }

    public static Query ltQuery(String key, Object value) {
        return new Query().lt(key, value);
    }

    public static Query lteQuery(String key, Object value) {
        return new Query().lte(key, value);
    }

    public static Query likeQuery(String key, Object value) {
        return new Query().like(key, value);
    }

    public static Query likePrefixQuery(String key, Object value) {
        return new Query().likePrefix(key, value);
    }

    public static Query likeSuffixQuery(String key, Object value) {
        return new Query().likeSuffix(key, value);
    }

    public static Query likeFullQuery(String key, Object value) {
        return new Query().likeFull(key, value);
    }

    public static Query inQuery(String key, Object value) {
        return new Query().in(key, value);
    }

    public static Query ninQuery(String key, Object value) {
        return new Query().nin(key, value);
    }

    public Query addCriteria(String key, Object value) {
        return this.addCriteria(key, Criteria.Opt.EQ, value);
    }

    public Query addCriteria(String key, Criteria.Opt opt, Object value) {
        return this.addCriteria(new Criteria(key, opt, value));
    }

    public Query addCriteria(Criteria criteria) {
        if (criterias == null) {
            criterias = new ArrayList<Criteria>();
        }
        this.criterias.add(criteria);
        return this;
    }

    public Query eq(String key, Object value) {
        return this.addCriteria(key, Criteria.Opt.EQ, value);
    }

    public Query neq(String key, Object value) {
        return this.addCriteria(key, Criteria.Opt.NE, value);
    }

    public Query gt(String key, Object value) {
        return this.addCriteria(key, Criteria.Opt.GT, value);
    }

    public Query gte(String key, Object value) {
        return this.addCriteria(key, Criteria.Opt.GTE, value);
    }

    public Query lt(String key, Object value) {
        return this.addCriteria(key, Criteria.Opt.LT, value);
    }

    public Query lte(String key, Object value) {
        return this.addCriteria(key, Criteria.Opt.LTE, value);
    }

    public Query like(String key, Object value) {
        return this.addCriteria(key, Criteria.Opt.LIKE, value);
    }

    public Query likePrefix(String key, Object value) {
        return this.addCriteria(key, Criteria.Opt.LIKE_PREFIX, value);
    }

    public Query likeSuffix(String key, Object value) {
        return this.addCriteria(key, Criteria.Opt.LIKE_SUFFIX, value);
    }

    public Query likeFull(String key, Object value) {
        return this.addCriteria(key, Criteria.Opt.LIKE_FULL, value);
    }

    public Query in(String key, Object value) {
        return this.addCriteria(key, Criteria.Opt.IN, Utilities.unique((Collection<?>) value));
    }

    public Query nin(String key, Object value) {
        return this.addCriteria(key, Criteria.Opt.NIN, Utilities.unique((Collection<?>) value));
    }

    public Query addSubCriteria(Criteria... subCriterias) {
        return this.addCriteria(new Criteria(subCriterias));
    }

    public Query addSubCriteria(Logical subQueryLogic, Criteria... subCriterias) {
        return this.addCriteria(new Criteria(subQueryLogic, subCriterias));
    }

    public Query addSubCriteria(Logical subQueryLogic, List<Criteria> subCriterias) {
        return this.addCriteria(new Criteria(subQueryLogic, subCriterias));
    }

    public Query addSort(String key, Direction dir) {
        if (this.sorts == null) {
            this.sorts = new LinkedHashMap<String, Direction>();
        }
        this.sorts.put(key, dir);
        return this;
    }

    public List<String> getGroups() {
        return groups;
    }

    public void setGroups(List<String> groups) {
        this.groups = groups;
    }

    public void addGroup(String... groups) {
        if (this.groups == null) {
            this.groups = new ArrayList<String>();
        }
        if (groups != null && groups.length > 0) {
            Collections.addAll(this.groups, groups);
        }
    }

    public Query start(Integer start) {
        this.start = start;
        return this;
    }

    public Query limit(Integer limit) {
        this.limit = limit;
        return this;
    }

    public List<Criteria> getCriterias() {
        return criterias;
    }

    public void setCriterias(List<Criteria> criterias) {
        this.criterias = criterias;
    }

    public Logical getLogical() {
        return logical;
    }

    public void setLogical(Logical logical) {
        this.logical = logical;
    }

    public LinkedHashMap<String, Direction> getSorts() {
        return sorts;
    }

    public void setSorts(LinkedHashMap<String, Direction> sorts) {
        this.sorts = sorts;
    }

    public Integer getStart() {
        return start;
    }

    public Integer getLimit() {
        return limit;
    }

    public Map<String, Object> getAttach() {
        return attach;
    }

    public void setAttach(Map<String, Object> attach) {
        this.attach = attach;
    }

    public void addAttach(String key, Object val) {
        if (this.attach == null) {
            this.attach = new HashMap<String, Object>();
        }
        this.attach.put(key, val);
    }

    public Map<String, Object> toMap() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put(MybatisContext.CRITERIAS, criterias);
        map.put(MybatisContext.QUERY_LOGIC, logical);
        map.put(MybatisContext.SORTS, sorts);
        map.put(MybatisContext.GROUPS, groups);
        map.put(MybatisContext.START, start);
        map.put(MybatisContext.LIMIT, limit);
        map.put(MybatisContext.ATTACH, attach);

        Map<String, String> opts = new HashMap<String, String>();
        for (Criteria.Opt opt : Criteria.Opt.values()) {
            opts.put(opt.alias(), opt.value());
        }
        map.put(MybatisContext.QUERY_OPTS, opts);
        return map;
    }

    public Map<String, Object> toMap(String tableName) {
        Map<String, Object> map = this.toMap();
        if (tableName != null && tableName.length() > 0) {
            map.put(MybatisContext.TABLE_NAME, tableName);
        }
        return map;
    }

    public Map<String, Object> toMap(String tableName, List<String> tableColumns) {
        Map<String, Object> map = this.toMap(tableName);
        if (tableColumns != null && tableColumns.size() > 0) {
            map.put(MybatisContext.TABLE_COLUMNS, Utilities.join(tableColumns, ","));
        }
        return map;
    }

    public Query copy() {
        Query query = new Query();
        if (Utilities.isNotEmpty(this.criterias)) {
            for (Criteria criteria : this.criterias) {
                query.addCriteria(criteria);
            }
        }
        query.setLogical(this.logical);
        if (this.sorts != null) {
            for (String key : this.sorts.keySet()) {
                query.addSort(key, this.sorts.get(key));
            }
        }
        if (this.groups != null) {
            query.addGroup(Utilities.toArray(groups));
        }
        query.start(start).limit(limit);
        if (this.attach != null) {
            for (String key : this.attach.keySet()) {
                query.addAttach(key, this.attach.get(key));
            }
        }
        return query;
    }

    /**
     * 字段排序
     */
    public static enum Direction {
        ASC("ASC"), DESC("DESC");

        private String value;
        Direction(String value) {
            this.value = value;
        }

        public String value() {
            return value;
        }
    }

    /**
     * 逻辑运算符
     *
     * @author sevendlong(lin23871@163.com)
     * @date 2014/9/30
     * @since V1.0
     */
    public static enum Logical {
        AND("AND"), OR("OR");

        private String code;

        Logical(String code) {
            this.code = code;
        }

        public String value() {
            return code;
        }
    }
}
